wave 1: six backlog lanes as one integration train (24 items close) - #310
Merged
Conversation
`_request` measured `len(base_url) + len(path)` against MAX_REQUEST_URL_LEN and then handed `params=` to httpx, which appends the query AFTER that check. Every read the console/harness/tray makes goes through `_get`, so a long filter value (a search needle, a control id) would, on first deployment, build an over-long request line with nothing refusing it -- the residual under the ASVS 4.2.5 `partial`. Build the request first (httpx's own resolution step) and measure str(request.url), then dispatch the built request through `send` -- which is what `request()` does internally, so the auth and follow-redirects client defaults are unchanged. The new bound is proved by a tripwire transport rather than a stub response: the claim is that the request never reaches the wire, and a stub 200 cannot tell that apart from a request that went out. Watched RED against the pre-fix code (the 9046-char URL reached the transport). A positive control pins that a short query still goes out, so a bound that refused every query-bearing GET would not pass. test_request_maps_non_2xx_to_apierror moves its stub from `_http.request` to `_http.send` -- the same transport seam, one call deeper.
…ution-tested (BACKLOG #1037)
remove.ps1 derived its repo root from $PSScriptRoot alone, so the only repository a
test could point it at was the real checkout -- which no test may drive, because the
script force-removes worktrees and force-deletes refs. Its branch-delete path was
therefore covered by review only, and it is the one place in scripts/worktree/ where
being wrong loses commits reachable from no ref and no reflog.
Adds -RepoRoot, the same parameter and the same reason prune-merged.ps1 already had:
default to the script own checkout, refuse a root that does not exist, otherwise
resolve it. Behaviour on the default path is unchanged.
tests/test_worktree_remove.py drives the real script as a subprocess against a
synthetic repo under tmp_path, never against this checkout. It covers both halves of
the lossless-delete discipline -- the branch that IS contained in origin/main and is
force-deleted after re-verification, and the branch holding unique commits that is
KEPT with its count reported -- plus the branch being read from git rather than from
the directory name, the detached-HEAD refusal, the untracked/.venv case, and the
$PSScriptRoot default itself (exercised against a copy inside the fixture).
Evidence, not assertion. The suite was run RED first: 9 of 10 failed with "A parameter
cannot be found that matches parameter name RepoRoot". Two mutations then proved the
assertions discriminate rather than merely survive:
branch -d -> branch -D (drop the re-verification)
test_delete_branch_keeps_a_branch_holding_commits_not_on_origin_main FAILED
-- the branch holding unique commits was destroyed.
$branch = git rev-parse HEAD -> $branch = $Name (name the directory, not the ref)
test_delete_branch_reads_the_branch_from_git_not_the_directory_name FAILED
test_detached_head_refuses_delete_branch_without_removing_anything FAILED
Both mutations were reverted; 10 passed.
Note for a later item, NOT fixed here: the git status --porcelain at the top of
remove.ps1 does not check its exit code, so an unreadable status is indistinguishable
from a clean tree and the script proceeds to --force. prune-merged.ps1 closed exactly
that fail-open in its own Test-WorktreeClean. Out of scope for #1037.
… #1048) The `auth` package carried zero outbound length measurement, so the one HTTP request the OIDC relying party makes -- the token exchange -- went to the opener unmeasured. Measure the request line and header block immediately before the POST and refuse with FlowError. `token_endpoint` is operator-static config (validated https at load), so this is the weaker of the two ASVS 4.2.5 limbs and the partial does not rest on it. It earns the bound anyway: an env() value that resolved to an unexpected blob now surfaces as a clear refusal instead of a wire-level surprise on the first federated login. No third copy of the limit. The guard calls transports/rest.py's find_outbound_length_violation -- the same measurement every other egress uses -- imported lazily so the pure, socket-free auth.oidc package takes no module-scope transports import, the containment store/keyprovider_vault.py already uses for the same helper. The raise stays FlowError so the caller's audited login-failure mapping still applies; only the violation class and the length are disclosed. Watched RED against the pre-fix code: the 9019-char request reached the tripwire opener. Two positive controls ship with it -- a normal endpoint must still reach the opener (so a guard that refused every exchange would not pass), and the shared bound is asserted to be the 8192 the refusal message quotes (so the number in the test is the shipped limit, not a coincidence).
new.ps1 anchors on $PSScriptRoot, so run from a linked worktree it creates the new
tree beside ITSELF -- under .claude/worktrees/, say -- and then told the reader to run
remove.ps1 from the primary. remove.ps1 anchors on its own location too, so the
primary copy derives <primary-parent>/<primary-leaf>-<Name>, a path that does not
exist, and throws "No such worktree". The anchoring is correct (#1060); the sentence
was not, and #1078 is explicit that the placement must not change.
new.ps1 now prints two commands, each carrying $RepoRoot explicitly so it works from
any cwd outside the worktree:
pwsh -NoProfile -File "<root>\scripts\worktree\remove.ps1" -Name <name>
git -C "<root>" worktree remove --force "<path>"
The first is the one to reach for -- it keeps the uncommitted-tracked-changes guard,
which the bare git call does not. --force on the second because the untracked .venv
makes git consider the worktree non-empty.
The same false claim was in remove.ps1's own header and in docs/WORKTREES.md; both are
corrected here, because fixing one site and leaving two is how a corrected fact comes
back.
ADVICE THAT IS ONLY READ IS ADVICE NOTHING CHECKS, so the test EXECUTES it.
tests/test_worktree_new_cleanup_advice.py extracts each printed command from the
script (an extraction contract new.ps1 now states on its side: " # " is a command,
" # " is prose), substitutes the paths, and runs it against a synthetic repo,
requiring the worktree to be gone AND deregistered.
Red first, and the control is live. All three tests failed before the change; the
third failed only AFTER reproducing the defect -- it builds a linked checkout `inner`,
creates `inner-feature` beside it the way new.ps1 would, and asserts the retired
advice still throws today:
assert retired.returncode != 0 PASSED
assert "No such worktree" in stderr PASSED
...naming <primary-parent>/repo-feature PASSED
assert not target.exists() FAILED <- no working advice yet
so the green it reports now is evidence the assertion can see the class. An
end-to-end run of the real new.ps1 (copied into a synthetic repo, -NoInstall, invoked
from the linked worktree) printed both commands with the linked root filled in, and
the first one removed the worktree verbatim.
…327, #1099, #1200, #1201, #1206, #1207, #1209, #64) Seven banners flipped to closed, each confirmed by OPENING THE SHIPPED CODE rather than by trusting a report or a commit message, plus one prose-only reconciliation. Confirmed in the code, not inherited: #1200 ci.yml carries alwayscode='\.(py|ps1|sh|ts|js|yml|yaml|toml|lock|cfg|ini)$' and evaluates it in the FIRST elif, ahead of alwayscodepath and noncode. Re-driven with the regexes read back out of ci.yml. #1201 _is_secret_header() ends in a substring test over auth|token|secret|credential|password|passphrase|key, with the five original names kept as a floor and a second VALUE arm for opaque vendor names. #1206 redacted_settings() has an odbc_params arm; _is_secret_odbc_key() is shape-based and case-insensitive, with the libpq PATH keywords excluded. #1207 _redact_header_value() opens with the EnvRef arm; _mask_url_userinfo() replaces only the password half. Both reach display_settings by delegation. #1209 the || binds the ASSIGNMENT, outside the substitution, plus a shape test. Re-executed under bash -e against a gh stub reproducing the stream split: pre-fix emits advisory_ok=true (FAIL OPEN), shipped emits false. #327 tests/test_private_paths_stay_ignored.py pins all six rules with a cardinality assertion. PROVED ABLE TO FAIL: removing /docs/security/ from .gitignore reds it by name; restored byte-clean. #327's residual is fixed in this commit. docs/SESSION-DRIFT-CONTROLS.md linked [.claude/settings.json](../.claude/settings.json) -- a path no reader outside the maintainer's machine has -- and presented the blanket-stage hook as an active control. The link is removed rather than repaired and the guard's real reach is stated: the script is tracked, its PreToolUse matcher is not, so a fresh clone and every worktree come up without it. link_check.py could never have caught that: .claude/ is in its WITHHELD set and the exemption continues BEFORE the counter increments. Measured by planting two hrefs -- a missing non-withheld path took the run red and the total 5359->5360; the same path under .claude/ left it green AND the total unchanged. The href was not resolved, it was never counted. #1099 corrects #1094's "the archival pass generates the anchor ... derive it from the generator". There is no archival tooling: the only tracked paths matching "archiv" are seven documents. Two corrections to #1099's own text, both the class it was filed about -- the sentence was in the LIVE file, not the archive, and it cited tests/test_archive_link_resolution.py, which is on no merged ref (PR #281 squashed as 6cb34f5 and the file landed as tests/test_link_resolution.py). Found by searching every ref. The same stale name in #1095's block was corrected with it. #64 is PROSE ONLY and STAYS OPEN. Step 1 ran 2026-07-12; step 2 is REFUSED, not gated (ADR 0055 withdrawn on two independent grounds, ADR 0107 closes Phase 4). The 2026-06-28 "honest verdict" figures are corrected in place: "compute unvalidated" is falsified (C5 pins per-shard R in [2,3) against the 3.62 a cleared N=16 needs), and "~7 commits/msg, group-commit unbuilt" is wrong in both halves (10.4746 committed_txns/msg measured; the commit tier is ~9% utilised). What survives is only the index role over #62/#63/#47/#34. Whether that umbrella is discharged is an owner call and was deliberately not taken. HELD FOR THE OWNER (G28): the two redaction route-onwards found under #1201 and #1206 -- an env() ref in a headers table, and env() resolution inside nested settings -- are marked "pending owner ledger decision" in place. No number was allocated and neither was quietly closed as prose. Gates: backlog_status_check 477 items / 282 live + 195 archive, link_check 5369 links across 347 files, ledger_check clean.
…042) Every other shipped HTTP egress routes through a no-redirect opener -- transports/rest.py's _NO_REDIRECT_OPENER, and auth/oidc_http.py's local twin for the IdP hop. The [vault] provider clients were the exception: they built an hvac.Client with no redirect policy, and hvac's default is allow_redirects=True. The token rides as an X-Vault-Token header on every Transit/KV call, so on a first deployment a 3xx from an on-path attacker (absent TLS integrity) or a spoofed Vault would relocate a request carrying it, while every default egress refused the same redirect. Set allow_redirects=False at both client-construction points. The third client named in the item, store/crypto_transit.py, already reuses keyprovider_vault's _build_client, so it inherits the policy from one construction point rather than growing a second. Verified against the real library rather than assumed -- hvac 2.4.0's Client.__init__ takes allow_redirects (default True), stores it on the adapter, and the adapter passes allow_redirects=self.allow_redirects to requests.Session.request. hvac is the optional [vault] extra and CI never installs it, so the committed tests stand a recording module in for it and assert what our code asks for; the measurement above is recorded in the test module's docstring because no test can reach it without the extra. Watched all three RED against the pre-fix code -- each reported the constructed kwargs as dict_keys(['url', 'token']), no redirect policy present. The Transit cipher is driven end to end through build_transit_cipher rather than asserted by identity, so a future private client construction there reds this test. A fourth test is the live positive control on the instrument: it builds a client without the policy through the same fake and asserts the recorder reports its absence, so a green above cannot be the fake swallowing an unrecognised kwarg.
…im (BACKLOG #327, #1099, #1200, #1201, #1206, #1207, #1209) Every item carrying a closed banner in docs/BACKLOG.md moves into docs/archive/backlog/BACKLOG-CLOSED.md: the 34 that were already closed-in-live plus the 7 closed in the preceding commit. live 282 -> 241 archive 195 -> 236 total 477, unchanged VERBATIM, and measured rather than asserted. Each block was SLICED, never re-rendered, so headings are byte-identical by construction -- 41/41 confirmed against the pre-move file, which is what keeps every #<n>-<slug> anchor resolving. Undoing the link rewrite reproduces the pre-move body exactly for 41/41. No item was lost (the pre-move set minus live minus archive is empty) and no number is duplicated across the two files. DEPTH IS +2, NOT +1. docs/BACKLOG.md -> docs/archive/backlog/BACKLOG-CLOSED.md crosses archive/ AND backlog/, so 84 relative hrefs took a ../../ prefix. That is what the existing archive already uses (../../adr/... for docs/'s adr/...), and the +1 form the plan called for is provably wrong: injecting one href at +1 takes link_check.py RED, resolving to docs/archive/adr/0004-payload-agnostic-ingress.md, which does not exist. Restored, green again -- the gate can see this class. The rewrite replicates link_check.py's OWN skip rules (fenced blocks; links whose "]" falls inside an inline code span), so the set of hrefs rewritten is exactly the set the gate checks. Rewriting more would corrupt displayed text; rewriting fewer would leave a checked link broken. Appended rather than inserted in numeric order, which is the archive's existing convention -- its tail already runs 348, 349, 350, 335, 233, 326. Gates: backlog_status_check 477 items (241 live + 236 archive), each declaring exactly one status; link_check 5369 links across 347 files, all resolving; ledger_check clean.
…he model
redact_unauthorized masked a property exactly where it was called, so the set of
protected surfaces was only ever the set of call sites someone remembered to write.
Coverage was pinned by an enumerated test, which by construction cannot cover the
route nobody has written yet: a new PHI-returning route that forgot the call would
have serialized summary / error / metadata / last_error / detail in full, with the
whole suite green.
The default now denies. api/phi_gate.PhiGatedModel withholds each declared PHI
property from JSON serialization until an authorization decision is recorded on the
instance; redact_unauthorized is what records one, releasing exactly the properties
the caller's permissions unlock. A forgotten call returns null - a functional defect
its author sees - instead of a leak nobody sees.
Mechanics, and why each was chosen over the obvious alternative:
- A named field serializer with a `str | None` return, not a model-level wrap
serializer. Measured against pydantic 2.13.4 / fastapi 0.141.1: a wrap serializer
collapses the model's whole serialization schema to
{"type": "object", "additionalProperties": true}, and an Any-returning field
serializer untypes its own property. The published OpenAPI must not get vaguer than
what the code returns; a test now pins that.
- when_used="json", so the gate covers every path by which one of these models reaches
a client (FastAPI response models, jsonable_encoder, model_dump_json) while leaving
python-mode model_dump alone. api/app.py composes MessageDetail from a MessageSummary
dump BEFORE any authorization decision exists; gating that dump would blank the
detail route for an authorized caller.
- Class creation refuses a gated name outside GATEABLE_PROPERTIES or absent from the
model's fields - the one way this gate could go quietly inert.
Policy stays in field_authz (which permission unlocks which property); the model
declares only that a property is gated. The two are pinned to each other in both
directions.
Evidence: with phi_gate present but the models unwired, the new end-to-end test went
red on exactly the item's failure mode - "a route with no redact_unauthorized call
returned PHI: {'summary': 'MRN9001 DOE^JANE', 'error': 'strict validation failed on
segment PID', 'metadata': ...}" - and green once the six models declared their gate.
Every null assertion ships with a released positive control, so none of them can pass
because serialization is simply broken. Synthetic HL7 only.
…estating it (BACKLOG #1099) The header read "moved here verbatim on 2026-08-03 so the published backlog is the ~92 items someone can actually act on" -- a PRESENT-TENSE count that was a measurement taken on one day and has drifted every day since. Measured 2026-08-10 the live file holds 241 open items, so the sentence a reader would have quoted was off by a factor of two and a half. Replaced with the derivation rather than a fresher number, because a fresher number rots identically: run parse_items from backlog_status_check.py over BOTH files. That is the same single-source rule the file already states two paragraphs down for reading the status banners, and the rule CLAUDE.md 11 states for the alphabet. Same class as #1099 -- a ledger document asserting something about itself that nothing re-derives.
"Two of its six clauses are falsified" / "The other four clauses stand" is a pair of counts that have to agree with each other and with a paragraph nobody will re-parse -- and the second one then named only three things. Replaced with "at least two" and "the remaining clauses", which is CLAUDE.md 11's rule (SDS-3.6: a completeness claim is a liability, prefer "at least" to an enumeration) applied to a sentence I had just written while correcting someone else's stale figures.
…s a safe path
XmlMessage.find / get / get_all / exists / set / set_attribute took only an
expression string into the sole sink self._root.xpath(...), and nothing in the tree
bound a value. XmlMessage is exported to code-first Handlers, Handlers are authored
by users, and all HL7/XML content is untrusted data - so an author filtering on a
message-derived value had no framework-provided alternative to an f-string, and an
f-string into an XPath predicate is an injection.
Every query method now takes $name bindings as keyword arguments:
msg.get("//record[@mrn=$mrn]/note/text()", mrn=untrusted)
A bound value is compared as a value and never parsed as expression syntax. The
expression (and set's value / set_attribute's name+value) are positional-only, so a
binding may legitimately be called "expression" or "value" without colliding with the
parameter. Only str/int/float/bool bind: lxml would also accept a node set, which
would let a caller feed one expression's result back in as a sub-expression - the
shape this API exists to remove. An unbound or unbindable variable surfaces as
XmlPathError, the codec's own data error, not a bare lxml traceback out of a
transform; the message names the variable and its type, never its value.
Evidence, both halves red first:
- The threat is demonstrated, not asserted. With mrn = "nope' or @mrn!='", the
interpolated form selects BOTH records where the author meant one -
test_interpolating_a_tainted_value_into_an_xpath_string_is_injectable passes today
and is the live positive control for the empty result below.
- The four binding tests failed with "XmlMessage.get_all() got an unexpected keyword
argument 'mrn'" before the change and pass after; the same payload bound as $mrn now
selects nothing, and the same call shape with a legitimate value still selects its
record, so the empty result is the binding working rather than the expression being
broken.
Invented MRNs throughout, never real PHI. This is a hardening item for the authoring
surface, not a shipped vulnerability: nothing in-tree reaches .xpath() with tainted
data.
`FileSource._move` relocated a processed file with `path.replace(_unique(dest))` -- a check-then-act pair, where `_unique` asked exists() and `replace` then overwrote whatever sat at the name it chose. The delivery path had already replaced that pattern with `_claim_unique`'s os.link/O_EXCL claim (FILE-5); the archive move was the caller left behind. Route it through the same claim. Claim-then-unlink rather than one rename: the atomic claim cannot be expressed as a rename, because renaming a file over its own hard link is a POSIX no-op and the original would survive. If the unlink fails after the claim, the file is archived AND left to be re-read -- the same duplicate-read outcome the pre-existing failure arm already had, logged the same way. `_claim_unique`'s cross-filesystem fallback now streams instead of read_bytes(): the archive move claims through it too, and an inbound file is only as small as max_file_bytes, which is unset by default -- buffering one whole to claim a name would put an arbitrarily large payload in memory on exactly the filesystems that already cannot hard-link. Scope, stated plainly: the default config cannot race this. One poller per source over an engine-owned processed_dir, and the raw message is durable in the store before the ACK regardless. It bites the non-default config the item names. RED evidence, two threads with a per-round barrier archiving same-named files into one processed_dir: pre-fix, "38 of 120 archived messages were lost or overwritten by the other source" (a standalone probe of the same shape measured 61, 61, 64, 63 and 62 of 120 across five runs). Post-fix: 120 of 120, five runs of five. Real threads rather than an injected interleaving, because the window only exists in the pre-fix code and a hook placed inside it could not survive the fix. Two deterministic controls ship with it -- the escalation still yields `m-1.hl7` without touching the file already there, and the original is still removed, so a `_move` that refused every taken name or that copied instead of moved would not pass. test_file_source_move_failure_leaves_file_in_place is repaired in the same commit, and it is the more interesting half. It patched Path.replace to force a failure and asserted the file was left in place -- but its inbox had no .processed dir, so the move ALSO failed for that reason. Once `_move` stopped calling Path.replace the injection went inert and the test kept passing on the missing directory alone. It now creates the archive dir (so the injection is the only possible cause) and patches the claim. Proved load-bearing: with the patch line removed it reds.
…asked question (BACKLOG #1087)
new.ps1 ran `git worktree add <path> -b <Branch> <Base>` with -Base defaulting to
origin/main. Git branch.autoSetupMerge then set the new branch upstream to the
remote-tracking BASE, so @{u} resolved to origin/main rather than to the branch own
remote ref -- and @{u}..HEAD reported a fully-pushed branch own commits as UNPUSHED,
forever. That number feeds the "is anything at risk if I delete this worktree" check,
so a routine safety question got a confidently wrong answer with no error anywhere.
The fix is the one flag #1087 names. push.default is NOT touched and must not be:
with the upstream wrong, push.default=upstream makes a bare push write the feature
branch onto main, and push to main is not blocked server-side here.
Measured on a synthetic repo with a real bare origin, both legs, branch tip byte-
identical to its pushed remote:
worktree add <path> -b feat-a origin/main
tip == origin/feat-a ? True
@{u} -> exit 0 : origin/main
@{u}..HEAD -> exit 0 : 1 <- FALSE unpushed commit
bare `git push` -> fatal, and git own remediation text reads
"git push origin HEAD:main"
worktree add --no-track <path> -b feat-b origin/main
tip == origin/feat-b ? True
@{u} -> exit 128 : no upstream configured <- loud, not wrong
after `git push -u origin feat-b`:
@{u} -> origin/feat-b
@{u}..HEAD -> 0
Loud beats wrong: an instrument that cannot answer is the correct failure direction.
The cost is that the first push is `git push -u origin <branch>`, which then sets the
upstream to the right value. Documented in new.ps1 and docs/WORKTREES.md.
tests/test_worktree_new_no_track.py asserts the DIVERGENCE, per #1000: it extracts the
creation command from new.ps1, runs it against a synthetic repo, pushes, and reads
@{u}. The control is the pre-fix command written out literally in the same test, so it
keeps reproducing the class whichever sanctioned fix new.ps1 later carries. Red first:
the control legs passed (upstream origin/main, count 1) and the subject leg failed on
`good_up == "origin/main"` -- the defect, in the assertion.
A third test scans every .ps1 under scripts/ (38 files, count printed on failure) and
refuses `push.default`. It was first written as a raw text scan and fired on new.ps1
own comment WARNING against push.default -- which is how the string match was shown to
work; it now skips comment lines, and a real `& git -C $RepoRoot config push.default
upstream` appended to new.ps1 was caught at line 262 before being reverted.
Two stale premises the fix created, corrected in the same commit rather than left:
* new.ps1 lock comment attributed the .git/config.lock race to the upstream write that
--no-track removes. The measurement was taken with tracking on and nobody has
re-measured without it, so the lock STAYS and the comment now says exactly that.
* prune-merged.ps1 signal 3 named new.ps1 as the source of the parent-upstream shape.
It is no longer. Note also the improvement: a new.ps1 branch upstream was pinned to
the base, which is never origin/<branch>, so signal 3 could never fire for one --
after `push -u` it can.
… (BACKLOG #1087)
The flag only applies at creation. Every worktree made before it still carries
@{u} = origin/main and nothing corrects them retroactively, so a reading taken in one
of those is still wrong -- and a reader who saw the fix land could reasonably conclude
otherwise. Follows the rule that a compensating control must not rest on a false
premise: the control here is "the upstream is right now", which is true only of
worktrees created since.
Both remediations are measured, not inferred, against the same synthetic fixture the
item was reproduced in:
git push -u origin feat-a @{u} origin/main -> origin/feat-a, count 1 -> 0
git branch --unset-upstream @{u} origin/main -> "fatal: no upstream configured"
… not enforcing Every doc-content assertion in tests/test_threat_model_doc_drift.py went silently inert when docs/security/THREAT-MODEL.md was absent - which on a public checkout it always is, since docs/security/** is deny-listed from the OSS mirror and vaulted. A bare pytest.skip in a 12,000-test run is one 's' among thousands: the run reads clean while ASVS 15.1.3 and 15.1.5, which are scored on that document, have no drift enforcement at all. That is ADR 0158's class 2 - a control that cannot report its own failure - and the fix has to add reporting without adding a false red on the tree where the absence is legitimate. Three changes, and deliberately not a fourth: - The absence is ANNOUNCED, once per run, as a ThreatModelDocUnenforced warning that names the path it looked for, every category of assertion that stopped enforcing, what still enforces, and the two env vars. It lands in pytest's warnings summary, which prints even under -q. The skip reason points at it. - MEFOR_THREAT_MODEL_DOC points the module at a copy elsewhere (the vault working tree), so the content half can be enforced from a checkout that does not carry the file. MEFOR_REQUIRE_THREAT_MODEL_DOC=1 makes absence a hard FAILURE, so a leg that is supposed to enforce fails closed rather than best-effort. - The CHECKER MECHANISM is now verified on every tree, doc or no doc, against a stand-in written in the module. Not a fourth: no public stand-in for the document's CONTENT. Copying the registries into a tracked fixture would be a green proving only that a fixture matches a fixture - one silent skip traded for a vacuous pass, which the item explicitly rules out. The stand-in proves the checkers can go RED, which is the property a skipped run stops evidencing, and nothing more. Evidence - the guard run in all three postures, and the new self-tests broken on purpose: - doc ABSENT: 12 passed, 89 skipped, 1 warning. The warning text is the receipt. - stand-in PRESENT (MEFOR_THREAT_MODEL_DOC at a scratch file): 79 failed, 21 passed, 1 skipped - the content half is live and names what it could not find, e.g. "15.1.5 no longer inventories these dangerous surfaces: ['_exec_module', '_assert_safe_config_source', 'db_lookup', ...]". A guard that reports 89 skips in one posture and 79 named failures in the other is demonstrably not asleep. - MEFOR_REQUIRE_THREAT_MODEL_DOC=1 with the doc absent: "Failed: MEFOR_REQUIRE_THREAT_MODEL_DOC is set, so this run is expected to enforce the threat model's content - but ...THREAT-MODEL.md does not exist." - The row-scoped checker was mutated to scan the whole section instead of its table rows - the regression this module was actually caught by once - and the new self-test went red with "the row-scoped checker did not notice a deleted row - it is asleep". Reverted; green after. The stand-in also caught the author: test_section_slicing_stops_at_the_next_same_or_ higher_heading was written asserting that a ### subsection is EXCLUDED from its ## slice. It is not, by design, and that inclusion is exactly why the anchor checks are scoped to table rows. The assertion now pins the real contract.
ASVS 15.1.3's "avoid building a response that takes longer than the consumer's
timeout" limb (properly 15.2.2) had no server-side enforcement. The only
asyncio.wait_for in api/ caps the connection-test probe; nothing bounded a
handler, so a slow one held its worker for as long as it ran and the client's own
timeout was the only thing that ever gave up -- which does not free the server.
No exposure on the shipping config (loopback, authenticated, single worker); this
is what a first deployment would need.
RequestTimeoutMiddleware, pure ASGI, refuses with 503 when the handler has not
begun responding within the deadline.
The bound is on BUILDING the response, not on sending it: the clock is cancelled
at http.response.start, so an attachment download or a large log body already
streaming is never cut mid-body over a slow link. That is also the limb ASVS
words -- the cost being bounded is the handler's, not the network's.
Registered directly inside ClientNetworkMiddleware and outside everything else,
so the deadline covers the attachment CSP re-assert, the console's own
middleware, the body cap, the security-headers middleware and every auth
dependency, while a refused address is still rejected before it can occupy a
deadline. That order is pinned by a test, not just described here. Sitting
outside _security_headers, the 503 sets the baseline headers itself rather than
being the one response in the API with none of them.
Default 120s: a runaway backstop, not a latency budget. Overridable per app via
app.state.request_timeout_seconds (<=0 disables) -- the seam a future [api] knob
would set, so a deployment with a genuinely long admin operation raises it
instead of losing the backstop everywhere. Not wired to config in this change.
Watched RED against the unregistered code: the slow route returned 200 with
{"status": "finished"} after running to completion. Positive controls ship with
it -- a fast handler under the same deadline still returns its own 200, a
disabled deadline lets the slow handler finish, and a non-numeric state value
falls back to the default rather than disabling the control.
…ut not the backlog (BACKLOG #327) Archiving 41 closed items took test_present_tense_mirror_prose_does_not_grow from 54 to 55 and red, with ZERO new prose written. Cause: _HISTORICAL excluded docs/BACKLOG.md but not docs/archive/backlog/BACKLOG-CLOSED.md, so the same sentence counted or did not purely by which of the two ledger files it sat in -- and closing an item moves the block byte-identically from the excluded file into the counted one. Any archival pass could breach this ratchet. It is also unfixable where it lands. The archive's invariant is that each block is byte-identical to the one that left BACKLOG.md, which is what keeps its #<n>-<slug> anchors resolving. So an archive hit can be counted forever and never edited away. A ratchet that can fire and can never be cleared is one that eventually gets suppressed -- the outcome this module's own docstring warns about. VERIFIED BEFORE EXCLUDING, because excluding a file to hide a real hit is the failure mode here. All three archive hits are false positives of the kind already recorded above the regex: :1558 "0 downloads on a private repo" a DIFFERENT project's release channel :4744 "green on the mirror-nightly run" a CI job name :7470 "the mirror branch does not exist" a git branch in the scorecard repo None claims this repository is a mirror. No genuine hit is lost. CEILING LOWERED 54 -> 52, not left at 54 to bank the two. Honest count re-measured at 52 across 1533 tracked files. Banking slack is what turns a ratchet into a rubber stamp: rot would have to exceed the slack before anything reds, and nothing reports that the gate has gone quiet. PROVED STILL ABLE TO FIRE rather than assumed. Planting one genuine present-tense claim ("This repository is the public mirror of the private development repo") in a SCANNED file takes it red at 53 against the new ceiling of 52; removed, green again, the file byte-clean. Narrower domain, tighter threshold, still fires on the first new instance. SCOPE NOTE: this is the only file outside the ledger lane's list that this wave touched. It is here because the archive move surfaced the defect and the branch is otherwise red; the alternative -- editing the moved prose -- would have broken the verbatim invariant the whole archive rests on, and raising the ceiling is forbidden by this module in as many words.
Routing the archive move through `_claim_unique` left `_unique` with no callers in the module. It is not merely dead: it is the exists()-then-act helper the fix exists to retire, so leaving it in the file invites the next caller straight back onto the racy form. Verified orphaned rather than assumed -- a tree-wide search for the name finds only `harness/file_transport.py`'s own module-level function and `transports/remotefile.py`'s own method, neither of which imports this one, plus prose references in the #1046 docstring and test module. It was never in `__all__`.
The #1043 commit added one line of new "OSS mirror" prose to the module docstring, which took tests/test_cutover_slug_rot.py's present-tense ratchet from 54 to 55 against a ceiling of 54. Measured, not assumed: the same five modules run at origin/main fail 20 tests in this environment and on the branch 21, and the one difference is exactly test_present_tense_mirror_prose_does_not_grow. The other 20 are pre-existing here (installed-hook parity, dependabot allowset, workflow shell syntax) and reproduce unchanged at the base commit. Reworded rather than ceiling-raised - raising it is the rot the ratchet exists to stop. The warning text loses the same framing for a second reason: the repository is developed directly in the public remote now, so "deny-listed from the OSS mirror" is dead framing to be planting in new prose. "withheld from public checkouts" is what is actually true, and it is what an operator reading the warning needs.
…BACKLOG #1077) The hook told every session "No exact row, or isRunning is false -> SKIP that peer", then had the model file the outcome under the token NOT_RUNNING. Measured 2026-08-06: an isRunning:false peer was DELIVERED to and answered within one turn, while an isRunning:true peer QUEUED behind its in-flight turn. The field means "executing a turn right now", so as a reachability test it reads backwards -- the rule dropped exactly the peers most able to answer, and the receipt token recorded a delivery that would have succeeded under a word that reads "gone". scripts/coord/session-registry.ps1 already documented the correct reading, so the repo contradicted itself in the one place a model acts on it. - Drop the isRunning condition entirely. An exact cwd match is the whole test; a wrong id is loud, so cwd alone deciding costs nothing. - Drop the NOT_RUNNING token from the delivery-receipt vocabulary, leaving two. The rationale is a source comment, not a line of stdout: naming a retired token in the instruction is how a model learns it is available. - Record the measurement ONCE, in session-registry.ps1's header (the field's source of record). The hook and docs/WORKTREES.md now point at it instead of restating. - Correct the WORKTREES.md paragraph that counted isRunning:true for 1 of 6 registry-LIVE peers and read that as a reachability rate; it was a count of who happened to be mid-turn. Tests assert the EMITTED STRING, because the hook's entire product is the text it puts in front of the model. Watched both go RED against the pre-fix script first: the failure output quoted "_LISTED | NOT_RUNNING> TAB <sent | failed>" as what it scanned. The absence assertion is paired with a presence assertion so a hook that emitted no instruction at all could not satisfy it -- and it immediately earned its keep by failing an earlier draft of this change whose own explanatory line printed the retired token.
…#1098) The SessionStart banner printed each live peer as a bare 8-hex token before a bracketed branch. That is a REGISTRY SESSION ID and resolves to no git object -- but the banner's own `git worktree list` block, a few lines above, prints a REAL abbreviated SHA in exactly that shape, so the reader is taught the wrong meaning by the output itself. A session comparing "is that worktree ahead of mine" against it gets a git error at best, and the wrong tree if the prefix happens to resolve. Measured in the test, not asserted: the same run carries `<path> 8ba9b65 [master]` rows from `git worktree list`, keyed to `rev-parse HEAD` so the control is an object and not a shape. - session-context.ps1: the word `session` goes on the ROW, in both the live-peer roster and the "WHAT THEY ARE BUILDING" list. A legend would let the column mean one thing in one row and something else in the next, which is what the item forbids. - presence.ps1: a column HEADER instead, because that output is a fixed-width table -- one header governs every row, which is the same property. It names all four columns; naming only the ambiguous one leaves the rest unstated. Both tests assert the EMITTED TEXT, which is the only place either defect exists: every other test of these two scripts reads `-Json`, where the field is called `Short` and no ambiguity is possible. That is exactly how a reporting defect survives a green suite. Watched both go RED against the pre-fix scripts first; the presence failure printed the whole table it scanned. No consumer parses the human table -- mail.ps1, announce-session.ps1 and session-context.ps1 all invoke presence with `-Json`, which returns before it.
…ting its own writes (BACKLOG #1024) Two halves of one defect. 1. The glob was unanchored. `-Filter ".claude-account-*"` matches any directory whose name merely BEGINS with `.claude-account-`, and `~/.claude-account-2.lock` IS a directory -- so the installer wrote gate wiring into a dir with no .claude.json, no .credentials.json and no sessions, i.e. one nothing has ever launched from, and re-wrote it on every run. Now anchored on `\A\.claude-account- \d+\z`, the .NET spelling of the predicate #199 gave the Python reader (.NET `\Z` also matches before a trailing newline; `\z` is the one that means Python's `\Z`). Writer and reader are now the same predicate. 2. -Status validated its own output. It scanned exactly $ConfigDir -- the set this script WRITES -- so a wrong discovery predicate made the writer manufacture the wiring and the reader read it back as correct. Both were wrong the same way, so they agreed: a validator satisfied by construction, ADR 0158's exact class. -Status now enumerates a DIFFERENT population: every ~/.claude* dir carrying a settings.json, judged by name afterwards rather than selected by name up front. It prints that population by name (a count that got smaller looks like an improvement; only the names say what stopped being looked at) and reports any dir outside the wire set that still carries gate wiring as ORPHAN GATE WIRING. Reported, never fixed -- deliberately. Anchoring the writer also puts the existing artifact out of -Uninstall's reach, so the remedy is a command a human runs (`-Uninstall -ConfigDir "<path>"`); which dir is a stale artifact versus a config root this box really uses is the owner's call. Verification is by redirected HOME, not by installing: a session must NOT execute this installer for real, because it rewrites user-scope wiring for every session on the box. That constraint is why the item was scored difficulty 3, and it is why -Status sits above the CLAUDECODE refusal -- auditing is not installing. Three tests, all watched RED against the pre-fix script first; the failure output carried the whole pre-fix -Status text, showing `.claude-account-2.lock` on a `wiring :` line and `scanned 3 config dir(s)`. - The writer/reader agreement test runs the REAL installer over the same name corpus the reader's own negative control uses and IMPORTS the reader's pattern rather than restating it. A restatement would be a third predicate, and this defect was two predicates disagreeing. It guards its own guard: the corpus must contain both an accepted and a rejected name, or the comparison is vacuous. - The orphan report is exercised on a decoy `.claude-account-2.lock`. - And the quiet arm is its negative control: same decoy, no settings.json, so the loud line must not fire. A line that appears on every run is one readers skip, which is how a real orphan would go unnoticed.
…(BACKLOG #1018) The two regexes that read worktree_gate.ps1 as text and extract every tool it branches on are implemented THREE times -- test_install_gate_wiring.py, test_gate_installed_parity.py, and Get-HandledTools in install-gate.ps1 (PowerShell) -- with nothing tying them together. They compute the same quantity, so this is duplication, not resemblance. They do NOT disagree today. The hazard is a one-sided edit, and what makes it worth a test is the FAILURE DIRECTION: an under-matching copy 2 shrinks `required` in test_every_non_optional_rule_is_wired_in_every_config_dir, so it passes having checked less; an under-matching copy 3 prints no UNWIRED line from -Status. Both are false greens, in the files written because a rule once shipped dead while 85 tests stayed green -- and neither is visible from inside the copy that changed. NOT UNIFIED, deliberately and per the item. A shared Python helper cannot absorb the PowerShell copy, so the honest end state is one helper plus a cross-language agreement test; this is that second half, and it stands alone. Each real implementation is driven as it stands over one corpus -- copy 1 through its module constant, copy 2 by its text argument, copy 3 by lifting its function out of the installer with the PowerShell AST and defining it in a fresh session (so the installer, which rewrites machine-global wiring, is never executed). A regex that carved the function body out by text would be a text scan of a text scanner. Nothing here re-implements the regexes: a fourth copy written to test the other three would be the same defect with better manners. Three arms: - The REAL gate. All three must agree, and each must return something -- three implementations that all return the empty set agree perfectly and measure nothing. - Six regex shapes (-in, -notin, spacing, single quotes, a hyphenated name, two branches). Each asserts the EXPECTED set, not just mutual agreement: three copies agreeing on the wrong answer is a state this file would otherwise call healthy. - The ONE known divergence, pinned with its reason. Copy 2 alone drops whole-line # comments, because the real gate quotes rule 4's condition in prose as well as in code. On the real gate that difference is invisible, which is exactly why it needs a corpus that separates it. Pinned, it becomes a stated property; any change to it fails here and has to be argued rather than absorbed. RED FIRST, both directions, exactly the demonstration the item prescribes. Changing the quote pattern to `"([A-Z][a-z]+)"` in copy 3 failed 5 of 8, printing 1 ...tools_the_gate_handles: ['GhostTool', 'RealTool'] 2 ...handled_tools: ['RealTool'] 3 ...Get-HandledTools: [] and the same change in copy 2 produced the mirror image, with [] on line 2 and both names on 1 and 3. Three of the eight stayed green under each perturbation, because that pattern under-matches only camel-case and hyphenated names -- so the test says which construct broke rather than only that something did.
…over an empty population (BACKLOG #1024) Found by reading back my own output. With no ~/.claude* dir carrying a settings.json the audit fell through to "every dir found is in the wire set above; nothing is unjudged" -- reassurance derived from nothing having been measured, which is the exact class the audit was added to remove. Measured, not reasoned: reverting the guard and re-running prints that line under `audit : 0 ~/.claude* dir(s)`. "I found nothing" and "I found things and they are all fine" print identically the moment a scan reports only its verdict. presence.ps1 already draws this distinction between an empty roster and an unavailable one; the audit now draws it too, saying NOTHING EXAMINED and naming the home it looked under.
…OG #1054) The ADR 0087 sandbox child configured logging with a bare `logging.basicConfig`, whose handler carries no filters. Redaction here is a property of the HANDLER, not of the logger or the call site, so the child's records reached the engine's inherited stderr with neither PHI redaction nor CR/LF neutralization: the three filters `_install_phi_filters` puts on the engine's own handlers were simply absent in that process. `logging_setup` grows a public `configure_stderr_logging()` -- the same chain (RedactionFilter -> CredentialQueryScrubFilter -> ControlCharScrubFilter) and the same text formatter as `configure_logging`, bound to stderr because the caller's stdout is a binary channel. The worker calls it in place of `basicConfig`. Nothing is leaking today: `[sandbox].mode` defaults to "off" and there are zero deployments. On a first deployment that opted into mode="subprocess", a WARNING+ record emitted by admin-authored Router/Handler code would have carried message-derived content onto stream 1's own sink unredacted and un-neutralized. Measured, not reasoned. The new subprocess test imports the real worker module in a child process and asserts on its actual stderr; against the pre-fix code it fails with the defect visible in the assertion output -- the child emitted the synthetic `PID|1||100^^^H^MR||DOE^JANE^Q||19800101|F` verbatim, and the raw CR/LF put the injected text at column 0 on its own physical line. docs/PHI.md changes in this same commit because the fix closes the weakness the doc disclosed. `test_the_sandbox_worker_stderr_writer_is_disclosed` couples the two by construction: once the child is filtered it demands the "outside the filter chain" exclusion be dropped. That test is re-pinned both ways, with an added assertion that the child uses one mechanism or the other, so removing both cannot pass green. The sink-module inventory drops the worker for the same reason -- it no longer builds a sink, it asks logging_setup for one.
…#1055) `sys.excepthook` covers the main thread only. The interpreter routes an exception that escapes a thread's run() to `threading.excepthook` and nowhere else, and that was still the stdlib default, which prints a raw traceback straight to stderr -- past the handler filter chain, since it never builds a LogRecord at all. `last_resort` grows `install_thread_excepthook()`, routing through `safe_exc` to the filtered log and naming the thread, and serve() installs it beside the existing `install_excepthook()`. SystemExit is ignored, matching the stdlib default: a thread calling sys.exit() is a clean exit, not an error to report. The engine thread that motivates it is the sandbox session's raw stdout reader, whose except clause catches only OSError by design, so anything else escapes run() and the frame bytes it was mid-read on are message-derived. Nothing is leaking today -- zero deployments. On a first deployment an unexpected non-OSError there would have put an unredacted traceback into the NSSM-captured stderr. Measured with a live positive control reproducing that shape, run against the pre-fix and post-fix trees. Before: the stdlib hook wrote 838 bytes of traceback ending in the full synthetic `PID|1||100^^^H^MR||DOE^JANE^Q||19800101|F`. After: 93 bytes reading `last-resort: uncaught exception in thread 'mefor-sandbox-reader': ValueError: PID|[redacted]` -- thread name and exception type kept, body gone. The committed test drives a real thread rather than calling the hook directly, and asserts stderr carries no traceback at all.
The coordinator's integration commit, and the single point where the "a PR that implements BACKLOG #N must update BACKLOG.md" required context is satisfied for the whole train. Lanes 4, 5 and 6 touch messagefoundry/ and none of them may edit the ledger, so as independent pull requests three of the six would have gone red on that context. Banner text is each lane's own, carried verbatim from its report rather than re-written here: the lane that measured the fix is the one that should describe it. Ledger after this commit, re-derived with parse_items rather than carried forward: live 241, open 224, closed-in-live 17, archive 236, namespace 477 conserved. Item 64 stays OPEN by instruction -- only its index role over 62/63/47/34 survives, and discharging that umbrella is an owner call.
…ing (BACKLOG #1024) CI caught this on the ubuntu leg and no Windows run could have. Get-ChildItem omits hidden entries without -Force. On Windows a dot-prefixed directory carries no hidden ATTRIBUTE, so every ~/.claude-account-N enumerates either way. On Linux the dot prefix IS the hidden convention, so the glob returned NOTHING and the wire set collapsed to the single explicit ~/.claude candidate on the line above -- which is a Join-Path, not a glob, and so survived. That is exactly what the parity test reported: writer wires : ['.claude'] reader judges: ['.claude', '.claude-account-1', '.claude-account-42'] and it is the defect #1024 exists to close -- two enumerations of the same population disagreeing. Get-ConfigCandidates already passed -Force; this is the matching half. The anchoring introduced earlier is correct and unchanged. Also adds a cross-platform arm to the test file. The existing parity test can only go red where dot-dirs are hidden, so on Windows it is a claim rather than a control: the one instrument able to see this defect was a CI leg. The new test sets FILE_ATTRIBUTE_HIDDEN explicitly via attrib +H, making the class reproducible on the box the code is written on, and asserts loudly rather than skipping if the attribute cannot be set -- a silent skip would restore the blindness it removes. Red-first, on Windows, with -Force reverted: FAILED test_a_hidden_account_dir_is_still_wired "a HIDDEN ~/.claude-account-N was not wired" and the -Status audit printed "scanned 1 config dir(s)" with ORPHAN GATE WIRING in .claude-account-1 -- the same shape CI reported. Restored: 11 passed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wave 1 of the backlog-clearing plan, as a single integration branch rather than six lane PRs.
BACKLOG #1018 #1024 #1037 #1042 #1043 #1044 #1045 #1046 #1047 #1048 #1049 #1054 #1055 #1077 #1078 #1087 #1098
BACKLOG #327 #1099 #1200 #1201 #1206 #1207 #1209
Why one PR and not six
backlog-hygienefires when a PR carries aBACKLOG #Ntoken and its three-dot diff touchesmessagefoundry/,ide/ormessagefoundry_webconsole/, and it then requiresdocs/BACKLOG.mdinthe same diff. Three of the six lanes touch engine code, and none of them may edit the ledger --
docs/BACKLOG.mdhad a single sole-writer lane for the wave, which is what keeps concurrent banneredits from colliding. As independent PRs those three could not satisfy the required context. The train
carries the engine changes and the banner commit together, so the context is satisfied once, in one CI
cycle instead of six.
Contents
Six lanes, each verified contained in the train by
merge-base --is-ancestor, plus a final commitflipping 17 banners. 32 commits, 52 files.
Verification
merge-tree --write-treeagainst a known-conflicting branch returns exit 1, so a 0 here means cleanrather than blind.
w1-integrationmerges cleanly ontomain(merge-treeexit 0, re-confirmed after a fresh fetch).parse_itemsrather than carried forward: live 241, open 224,closed-in-live 17, archive 236 -- namespace 477 conserved. No duplicate numbers, and no item
declaring more than one status.
--no-verifyand no hooksPath override.with the scan's negative control confirmed firing on synthetic bad input. The one session-slug token
in the diff is a net-zero move between the live ledger and the archive, and is already present on
main.Note for reviewers
#64is reconciled by this change but deliberately stays OPEN, by owner instruction. Please do notclose it as tidy-up.
One cross-lane file overlap exists and is benign:
docs/WORKTREES.mdis touched by two lanes, with thehunks roughly 500 lines apart.